#region Header File
//
// ImaEncoder.cs - IMA audio encoder
//
// Copyright (C) Javier Valcarce. BSD License
#endregion

#region Using Statements
using System;
using System.Diagnostics;
#endregion


namespace Dsp
{

    /// <summary>
    /// IMA audio codec (Encoder)
    /// </summary>
    public class ImaEncoder
    {
        sbyte index; // index for step table          __state
        int   pred;  // predicted signal (acumulated) __state

        sbyte[] indexTable = { -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8 };
        short[] deltaTable = { 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 
            31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 
            173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 
            724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 
            2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 
            9493, 10442, 11487, 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 
            27086, 29794, 32767};

        #region Constructors
        public ImaEncoder()
        {
            // Set initial state
            index = 0;
            pred  = 0;
        }
        #endregion

        /// <summary>
        /// Encodes a sample
        /// </summary>
        /// <param name="sample"></param>
        /// <returns></returns>
        public byte Encode(short sample)
        {
            int   e;     // residue, to be quantized
            int   er;    // residue, reconstructed
            byte  eq;    // residue, quantized (only 4 LSB are used)            
            short step;  // quantizer step
            byte  mask;

            // compute     the residue
            e = sample - pred;

            // quantize    the residue
            // reconstruct the residue
            if (e >= 0)
                eq = 0;
            else
            {
                eq = 8; // set sign bit
                e = -e;
            }

            mask = 4;
            step = deltaTable[index];
            for (int j = 0; j < 3; j++)
            {
                if (e >= step)
                {
                    eq |= mask;
                    e -= step;
                }

                step >>= 1;
                mask >>= 1;
            }
            /**/
            er = 0;
            step = deltaTable[index];
            if ((eq & 4) != 0) er += step;
            if ((eq & 2) != 0) er += step >> 1;
            if ((eq & 1) != 0) er += step >> 2;

            er += (step >> 3);
            if ((eq & 8) != 0)
                er = -er;

            // accumulate the residue (predicted sample)
            pred += er;
            /**/
            if (pred > +32767) pred = +32767;
            else if (pred < -32768) pred = -32768;

            // update index
            index += indexTable[eq];
            if (index < 00) index = 00;
            if (index > 88) index = 88;

            return eq;
        }

        /// <summary>
        /// Encodes a sample
        /// </summary>
        /// <param name="sample"></param>
        /// <returns></returns>
        public byte Encode(double sample)
        {
            // clip
            if (sample > +1.0) sample = +1.0;
            if (sample < -1.0) sample = -1.0;

            short s = (short) (sample * 32767);
            return Encode(s);
        }

        /// <summary>
        /// IMA-ADPCM Encoder
        /// </summary>
        /// <param name="ibuf">Input audio sample 16-bit C2 audio sample array</param>
        /// <returns>Compressed audio array. Each compressed audio sample is a 4-bit code stored
        /// in the 4 least significant bits of each byte, the other bits are zero</returns>
        public byte[] EncodeOne(short[] ibuf, int offset, int count)
        {
            byte[] obuf = new byte[count];

            for (int i = 0; i < count; i++)
            {
                obuf[i] = Encode(ibuf[offset + i]);
            }

            return obuf;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ibuf"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public byte[] EncodeOne(double[] ibuf, int offset, int count)
        {
            byte[] obuf = new byte[count];

            for (int i = 0; i < count; i++)
            {
                obuf[i] = Encode(ibuf[offset + i]);
            }

            return obuf;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ibuf"></param>
        /// <param name="offset"></param>
        /// <param name="count">Must be ODD</param>
        /// <returns></returns>
        public byte[] EncodeTwo(short[] ibuf, int offset, int count)
        {
            Debug.Assert(count % 2 == 0);
            byte[] obuf = new byte[count / 2];
            byte s0;
            byte s1;

            for (int i = 0; i < count/2; i++)            
            {
                s0 = Encode(ibuf[offset + 2*i + 0]);
                s1 = Encode(ibuf[offset + 2*i + 1]);
                obuf[i] = (byte) ((s1 << 4) | s0);
            }

            return obuf;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ibuf"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public byte[] EncodeTwo(double[] ibuf, int offset, int count)
        {
            Debug.Assert(count % 2 == 0);
            byte[] obuf = new byte[count / 2];
            byte s0;
            byte s1;

            for (int i = 0; i < count / 2; i++)
            {
                s0 = Encode(ibuf[offset + 2 * i + 0]);
                s1 = Encode(ibuf[offset + 2 * i + 1]);
                obuf[i] = (byte)((s1 << 4) | s0);
            }

            return obuf;
        }


        /// <summary>
        /// Set the internal state
        /// </summary>
        /// <param name="pred">Predicted sample</param>
        /// <param name="index">Index in the Index-ROM</param>
        public void SetState(int pred, sbyte index)
        {
            this.index = index;
            this.pred  = pred;
        }

        /// <summary>
        /// Gets codec's internal state
        /// </summary>
        /// <param name="pred"></param>
        /// <param name="index"></param>
        public void GetState(out int pred, out sbyte index)
        {
            index = this.index;
            pred  = this.pred;
        }
    }
}
